/**
 * An eventManager, but typed to prevent errors.
 * 简单的事件管理器，但支持类型检查，避免错误。
 * @example
 * type Payload = {ready: boolean}
 * const networkStateChange = new TypedEvent<Payload>()
 * const handler = (payload: Payload) => console.log(payload)
 * event.sub(handler)
 * event.dispatch({ready: true})
 * event.unsub(handler)
 * @example
 * const misakaStateChange = new TypedEvent<{selfDestructionInProgress: boolean}>()
 * const unsub = event.sub(console.log) // returns unsub function without define handler outside.
 * unsub()
 * @example
 * export const eventBus = {
 *   alice: new TypedEvent(),
 *   bob: new TypedEvent<{isE2eEncryption: boolean}>()
 * }
 * eventBus.bob.dispatch({isE2eEncryption: true})
 *
 * @class TypedEvent
 * @member sub      Subscribe to event. Returns an unsub method that original callback is not needed.
 *                  订阅事件。返回一个取消订阅方法，该方法不需要原始回调。
 * @member unsub    Unsubscribe to event. Require original callback.
 *                  取消订阅事件。需要原始回调。
 * @member dispatch Simply dispatch payload to every subscriber.
 *                  触发事件。
 * @member once     Only subscribe once.
 *                  只订阅一次。
 */

type cb<T> = (payload: T) => void | Promise<void>;

export class TypedEvent<T = void> {
  constructor(historyLength = 0) {
    this.historyLength = historyLength;
  }

  private readonly historyLength: number;
  private readonly history: T[] = [];
  private readonly cbs: Array<cb<T>> = [];

  public sub(cb: cb<T>) {
    this.cbs.push(cb);
    if (this.historyLength && this.history.length > 0) cb(this.history[0]);
    return () => this.unsub(cb);
  }

  public unsub(cb: cb<T>) {
    const index = this.cbs.indexOf(cb);
    if (index === -1) return;
    this.cbs.splice(index, 1);
  }

  public dispatch(payload: T) {
    if (this.historyLength) {
      this.history.unshift(payload);
      this.history.splice(this.historyLength);
    }
    return this.cbs.map((v) => v(payload));
  }

  public once(cb: cb<T>) {
    this.sub((arg: T) => {
      cb(arg);
      this.unsub(cb);
    });
  }
}
